Published on

Cobain Memory Cache dengan Golang

Authors

Overview

Untuk membangun API yan memiliki performa bagus, memang diperlukan proses optimasi pada API, salah satunya caching. Namun caching dapat dilakukan di memory (in-memory caching). Untuk implementasi in memory cache memang membutuhkan effort lagi deengan membuat utilitas code yang membantu proses cache. Dalam cache diperlukan untuk menyimpan berbagai jenis tipe data(string, int, float). Namun karena berbeda jenis tipe data maka diperlukan membuat fungsi berulang berdasarkan tipe data. Semenjak go versi 1.18 support untuk konsep generic kita tidak perlu menulis fungsi lagi berdasarkan setiap fungsi. hanya perlu menulis 1 fungsi yang dapat digunakan oleh berbagai tipe data dan tetap type-safety. Sehingga mengurangi kompleksitas dan kode berulang.

Ada 2 jenis cache yang dapat kita implementasi yaitu Long-Live-Cache dan Expiring-Cache

1. Long Live Cache

Jika kita memang ingin untuk melakukan cache dengan data yang tidak banyak berubah maka ini adalah salah satu dapat digunakan, keuntungnnay kita dapat mengurangi operasi hit data ke db dengan data statis.


package cache

import (
    "sync"
    "time"
)

type Cache[K compareable, V any] struct{
    items map[K]V
    mu sync.Mutex
}

func New(K compareable, V any)() *Cache[K,V] {
    return &Cache[K, V]{
        items: make(map[K]V),
    }
}

func (c *Cache[K,V]) Set(key K, value V) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key]=value
}

func (c *Cache[K,V]) Get(key K)(V, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()
    value, found := c.items[key]
    return value, found
}
func (c *Cache[K,V]) Remove(key K) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.items, key)
}
func (c *Cache[K,V]) Pop(key K) {
    c.mu.Lock()
    defer c.mu.Unlock()
    value, found := c.items[key]

    if found {
        delete(c.items, key)
    }
    return value, found
}


func main() {
	
	myCache := cache.New[string, int]()

	
	myCache.Set("one", 1)
	myCache.Set("two", 2)
	myCache.Set("three", 3)

	
	value, found := myCache.Get("two")
	if found {
		fmt.Printf("Value for key 'two': %v\n", value)
	} else {
		fmt.Println("Key 'two' not found in the cache")
	}

	poppedValue, found := myCache.Pop("three")
	if found {
		fmt.Printf("Popped value for key 'three': %v\n", poppedValue)
	} else {
		fmt.Println("Key 'three' not found in the cache")
	}

	myCache.Remove("one")

	removedValue, found := myCache.Get("one")
	if found {
		fmt.Printf("Value for key 'one': %v\n", removedValue)
	} else {
		fmt.Println("Key 'one' not found in the cache (after removal)")
	}
}


2. Expiring Cache

Untuk membuat Expiring cache kita perlu menambahkan properti expired guna sebagai penampung data waktu dan pengecekan cache expired.

package main

type item[V any] struct{
    value v
    expiry time.Timess
}
type item[V any] struct {
    value v
    expiry time.Time
}
func (i item[V]) isExpired() bool {
    return time.Now().After(i.expiry)
}
type TTLCache[K comparable, V any] struct {
    items map[K]item[V] 
    mu    sync.Mutex    
}



func NewTTL[K comparable, V any]() *TTLCache[K, V] {
    c := &TTLCache[K, V]{
        items: make(map[K]item[V]),
    }

    go func() {
        for range time.Tick(5 * time.Second) {
            c.mu.Lock()

            
            for key, item := range c.items {
                if item.isExpired() {
                    delete(c.items, key)
                }
            }

            c.mu.Unlock()
        }
    }()

    return c
}



func (c *TTLCache[K, V]) Set(key K, value V, ttl time.Duration) {
    c.mu.Lock()
    defer c.mu.Unlock()

    c.items[key] = item[V]{
        value:  value,
        expiry: time.Now().Add(ttl),
    }
}


func (c *TTLCache[K, V]) Get(key K) (V, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()

    item, found := c.items[key]
    if !found {
        
        return item.value, false
    }

    if item.isExpired() {
        
        
        delete(c.items, key)
        return item.value, false
    }

    
    return item.value, true
}


func (c *TTLCache[K, V]) Remove(key K) {
    c.mu.Lock()
    defer c.mu.Unlock()

    
    delete(c.items, key)
}


func (c *TTLCache[K, V]) Pop(key K) (V, bool) {
    c.mu.Lock()
    defer c.mu.Unlock()

    item, found := c.items[key]
    if !found {
        
        return item.value, false
    }

    
    delete(c.items, key)

    if item.isExpired() {
        
        return item.value, false
    }

    
    return item.value, true
}


func main() {
	myTTLCache := cache.NewTTL[string, int]()

	myTTLCache.Set("one", 1, 5*time.Second)
	myTTLCache.Set("two", 2, 10*time.Second)
	myTTLCache.Set("three", 3, 15*time.Second)

	value, found := myTTLCache.Get("two")
	if found {
		fmt.Printf("Value for key 'two': %v\n", value)
	} else {
		fmt.Println("Key 'two' not found in the cache or has expired")
	}

	time.Sleep(7 * time.Second)

	expiredValue, found := myTTLCache.Get("one")
	if found {
		fmt.Printf("Value for key 'one': %v\n", expiredValue)
	} else {
		fmt.Println("Key 'one' not found in the cache or has expired")
	}

	poppedValue, found := myTTLCache.Pop("two")
	if found {
		fmt.Printf("Popped value for key 'two': %v\n", poppedValue)
	} else {
		fmt.Println("Key 'two' not found in the cache or has expired")
	}

	myTTLCache.Remove("three")
}